// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;

using Tierless.Framework.Utilities;
using Tierless.Framework.Entities;

namespace Tierless.Framework.Data
{
	/// <summary>
	/// Provides an abstract base class for representing entity requests.
	/// </summary>
	[DataContract]
	[KnownType(typeof(EntityGetRequest))]
	[KnownType(typeof(EntityGetByIDRequest))]
	[KnownType(typeof(EntityGetByMasterIDRequest))]
	public abstract class EntityRequest
	{
		#region Fields

		private DateTime _asOf = DateTime.Today;
		private string _entityTypeName;
		private EntityTraversal _traversal;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the EntityRequest class.
		/// </summary>
		public EntityRequest()
			: this(String.Empty, DateTime.Today, null)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to request.</param>
		/// <param name="asOf">The effective date for the request.</param>
		/// <param name="traversal">An entity traversal describing related entities to be included in the request.</param>
		public EntityRequest(string entityTypeName, DateTime asOf, EntityTraversal traversal)
		{
			_entityTypeName = entityTypeName;
			_asOf = asOf;
			_traversal = traversal;
		}

		/// <summary>
		/// Initializes a new instance of the EntityRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to request.</param>
		public EntityRequest(string entityTypeName)
			: this(entityTypeName, DateTime.Today, null)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to request.</param>
		/// <param name="traversal">An entity traversal describing related entities to be included in the request.</param>
		public EntityRequest(string entityTypeName, EntityTraversal traversal)
			: this(entityTypeName, DateTime.Today, traversal)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to request.</param>
		/// <param name="asOf">The effective date for the request.</param>
		public EntityRequest(string entityTypeName, DateTime asOf)
			: this(entityTypeName, asOf, null)
		{}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the name of the entity type being requested.
		/// </summary>
		[DataMember]
		public string EntityTypeName
		{
			get { return _entityTypeName; }
			set { _entityTypeName = value; }
		}

		/// <summary>
		/// Returns the name of the entity set that will be returned by the request.
		/// </summary>
		public virtual string EntitySetName
		{
			get { return _entityTypeName; }
		}

		/// <summary>
		/// Gets or sets the effective date of the request.
		/// </summary>
		[DataMember]
		public DateTime AsOf
		{
			get { return _asOf; }
			set { _asOf = value; }
		}

		/// <summary>
		/// Gets or sets the entity traversal for the request.
		/// </summary>
		[DataMember]
		public EntityTraversal EntityTraversal
		{
			get { return _traversal; }
			set { _traversal = value; }
		}

		/// <summary>
		/// Gets or sets whether or not the data to be retrieved in the request is intended for update by the caller.
		/// </summary>
		public bool ForUpdate { get; set; }

		#endregion

		#region Methods

		protected virtual void InternalToString(StringBuilder builder)
		{
			builder.AppendFormat("{0}:{1}:{2:0000}{3:00}{4:00}", GetType().Name, EntityTypeName,
				AsOf.Year, AsOf.Month, AsOf.Day);
			if (_traversal != null)
			{
				builder.AppendFormat(", EntityTraversal: {0}", _traversal.TraversalType);
				if (_traversal.RelatedEntityTypeNames != null)
					foreach (var relatedEntityTypeName in _traversal.RelatedEntityTypeNames)
						builder.AppendFormat(", {0}", relatedEntityTypeName);
			}
		}

		public override string ToString()
		{
			var builder = new StringBuilder();
			InternalToString(builder);
			return builder.ToString();
		}

		public override bool Equals(object obj)
		{
			var request = obj as EntityRequest;
			return (request != null)
				&& (GetType().Name == request.GetType().Name)
					&& (EntityTypeName == request.EntityTypeName)
						&& (AsOf == request.AsOf)
							&& ((_traversal == null) == (request._traversal == null))
								&& ((_traversal == null) || _traversal.Equals(request._traversal));
		}

		public override int GetHashCode()
		{
			return
				(
					(
						(
							(
								(GetType().Name.GetHashCode() * 83)
									+ EntityTypeName.GetHashCode()
								)
									* 83
							)
								+ AsOf.GetHashCode()
						)
							* 83
					)
						+ (_traversal == null ? 0 : _traversal.GetHashCode());
		}

		#endregion Methods
	}

	public class EntityRequests : List<EntityRequest>
	{}

	/// <summary>
	/// Represents an optionally filtered request for entities of a given type.
	/// </summary>
	[DataContract]
	public class EntityGetRequest : EntityRequest
	{
		#region Fields

		[DataMember]
		private List<EntityParameter> _filter;

		private string _commandName;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the name of the command to be used to fulfill the request.
		/// </summary>
		[DataMember]
		public string CommandName
		{
			get { return _commandName; }
			set { _commandName = value; }
		}

		/// <summary>
		/// Gets the list of entity parameters that define the filter for the request.
		/// </summary>
		public List<EntityParameter> Filter
		{
			get { return _filter; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Sets the filter for the request.
		/// </summary>
		/// <param name="filter">The list of entity parameters that are to be used to filter the request.</param>
		public void SetFilter(params EntityParameter[] filter)
		{
			_filter = new List<EntityParameter>(filter);
		}

		protected override void InternalToString(StringBuilder builder)
		{
			base.InternalToString(builder);
			if (!String.IsNullOrEmpty(CommandName))
				builder.AppendFormat(", {0}", CommandName);
			if (_filter != null)
				foreach (var filterColumn in _filter)
					builder.AppendFormat(", {0}={1}", filterColumn.Name, filterColumn.Value);
		}

		public override bool Equals(object obj)
		{
			var request = obj as EntityGetRequest;
			return (request != null)
				&& base.Equals(obj)
					&& ((_commandName == null) == (request._commandName == null))
						&& ((_commandName == null) || (_commandName == request._commandName))
							&& ((_filter == null) == (request._filter == null))
								&&
								(
									(_filter == null) || _filter.All(AParam => request._filter.Contains(AParam))
								);
		}

		public override int GetHashCode()
		{
			var code = 0;

			if (_commandName != null)
				code = (code * 83) + _commandName.GetHashCode();

			if (_filter != null)
				code = _filter.Aggregate(code, (ACurrent, AParam) => (ACurrent * 83) + AParam.GetHashCode());

			return code;
		}

		#endregion

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="commandName">The name of the command to be used to retrieve the entities.</param>
		/// <param name="filter">The filter to be applied to the retrieval.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetRequest(string entityTypeName, string commandName, EntityParameter[] filter, DateTime asOf, EntityTraversal traversal)
			: base(entityTypeName, asOf, traversal)
		{
			CommandName = commandName;
			SetFilter(filter);
		}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filter">The filter to be applied to the retrieval.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		public EntityGetRequest(string entityTypeName, EntityParameter[] filter, DateTime asOf)
			: this(entityTypeName, null, filter, asOf, null)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="commandName">The name of the command to be used to retrieve the entities.</param>
		/// <param name="filter">The filter to be applied to the retrieval.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		public EntityGetRequest(string entityTypeName, string commandName, EntityParameter[] filter, DateTime asOf)
			: this(entityTypeName, commandName, filter, asOf, null)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filter">The filter to be applied to the retrieval.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetRequest(string entityTypeName, EntityParameter[] filter, EntityTraversal traversal)
			: this(entityTypeName, null, filter, DateTime.Today, traversal)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filter">The filter to be applied to the retrieval.</param>
		public EntityGetRequest(string entityTypeName, params EntityParameter[] filter)
			: this(entityTypeName, filter, DateTime.Today)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="commandName">The name of the command to be used to retrieve the entities.</param>
		/// <param name="filter">The filter to be applied to the retrieval.</param>
		public EntityGetRequest(string entityTypeName, string commandName, params EntityParameter[] filter)
			: this(entityTypeName, commandName, filter, DateTime.Today)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filterColumnName">The name of the column to be filtered.</param>
		/// <param name="filterColumnValue">The value of the column to be filtered.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetRequest(string entityTypeName, string filterColumnName, object filterColumnValue, DateTime asOf, EntityTraversal traversal)
			: this(entityTypeName, null, new[] { new EntityParameter(filterColumnName, filterColumnValue) }, asOf, traversal)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filterColumnName">The name of the column to be filtered.</param>
		/// <param name="filterColumnValue">The value of the column to be filtered.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		public EntityGetRequest(string entityTypeName, string filterColumnName, object filterColumnValue, DateTime asOf)
			: this(entityTypeName, filterColumnName, filterColumnValue, asOf, null)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filterColumnName">The name of the column to be filtered.</param>
		/// <param name="filterColumnValue">The value of the column to be filtered.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetRequest(string entityTypeName, string filterColumnName, object filterColumnValue, EntityTraversal traversal)
			: this(entityTypeName, filterColumnName, filterColumnValue, DateTime.Today, traversal)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="filterColumnName">The name of the column to be filtered.</param>
		/// <param name="filterColumnValue">The value of the column to be filtered.</param>
		public EntityGetRequest(string entityTypeName, string filterColumnName, object filterColumnValue)
			: this(entityTypeName, filterColumnName, filterColumnValue, DateTime.Today)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetRequest(string entityTypeName, DateTime asOf, EntityTraversal traversal)
			: this(entityTypeName, null, new EntityParameter[] { }, asOf, traversal)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		public EntityGetRequest(string entityTypeName, DateTime asOf)
			: this(entityTypeName, asOf, null)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetRequest(string entityTypeName, EntityTraversal traversal)
			: this(entityTypeName, DateTime.Today, traversal)
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		public EntityGetRequest(string entityTypeName)
			: this(entityTypeName, DateTime.Today)
		{}
	}

	/// <summary>
	/// Represents a request for a specific entity by ID.
	/// </summary>
	[DataContract]
	public class EntityGetByIDRequest : EntityRequest
	{
		/// <summary>
		/// Initializes a new instance of the EntityGetByIDRequest class.
		/// </summary>
		public EntityGetByIDRequest()
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetByIDRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="entityID">The ID of the entity to be retrieved.</param>
		public EntityGetByIDRequest(string entityTypeName, EntityID entityID)
			: base(entityTypeName)
		{
			_entityId = entityID;
		}

		/// <summary>
		/// Initializes a new instance of the EntityGetByIDRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="entityID">The ID of the entity to be retrieved.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		public EntityGetByIDRequest(string entityTypeName, EntityID entityId, DateTime asOf)
			: base(entityTypeName, asOf)
		{
			_entityId = entityId;
		}

		/// <summary>
		/// Initializes a new instance of the EntityGetByIDRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to be retrieved.</param>
		/// <param name="entityID">The ID of the entity to be retrieved.</param>
		/// <param name="asOf">The effective date of the retrieval.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be retrieved.</param>
		public EntityGetByIDRequest(string entityTypeName, EntityID entityId, DateTime asOf, EntityTraversal traversal)
			: base(entityTypeName, asOf, traversal)
		{
			_entityId = entityId;
		}

		private EntityID _entityId;

		/// <summary>
		/// Gets or sets the ID of the entity to be retrieved.
		/// </summary>
		[DataMember]
		public EntityID EntityId
		{
			get { return _entityId; }
			set { _entityId = value; }
		}

		protected override void InternalToString(StringBuilder builder)
		{
			base.InternalToString(builder);
			builder.AppendFormat(", ID:{0}", EntityId);
		}

		public override bool Equals(object obj)
		{
			var request = obj as EntityGetByIDRequest;
			return (request != null)
				&& base.Equals(obj)
					&& (_entityId == request._entityId);
		}

		public override int GetHashCode()
		{
			return base.GetHashCode() ^ _entityId.GetHashCode();
		}
	}

	/// <summary>
	/// Represents a request for a given entity type by a master/detail relationship.
	/// </summary>
	[DataContract]
	public class EntityGetByMasterIDRequest : EntityRequest
	{
		/// <summary>
		/// Gets or sets the ID of the master entity.
		/// </summary>
		[DataMember]
		public EntityID MasterID { get; set; }

		private string[] _detailColumnNames;

		/// <summary>
		/// Gets or sets the list of detail column names.
		/// </summary>
		[DataMember]
		public string[] DetailColumnNames
		{
			get { return _detailColumnNames; }
			set { _detailColumnNames = value; }
		}

		/// <summary>
		/// Initializes a new instance of the EntityGetByMasterIDRequest class.
		/// </summary>
		public EntityGetByMasterIDRequest()
		{}

		/// <summary>
		/// Initializes a new instance of the EntityGetByMasterIDRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type for the request.</param>
		/// <param name="detailColumnNames">The name of the detail columns for the request.</param>
		/// <param name="masterId">The ID of the master entity.</param>
		public EntityGetByMasterIDRequest(string entityTypeName, string[] detailColumnNames, EntityID masterId)
			: base(entityTypeName)
		{
			_detailColumnNames = detailColumnNames;
			MasterID = masterId;
		}

		/// <summary>
		/// Initializes a new instance of the EntityGetByMasterIDRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type for the request.</param>
		/// <param name="detailColumnNames">The name of the detail columns for the request.</param>
		/// <param name="masterId">The ID of the master entity.</param>
		/// <param name="asOf">The effective date of the request.</param>
		public EntityGetByMasterIDRequest(string entityTypeName, string[] detailColumnNames, EntityID masterId, DateTime asOf)
			: base(entityTypeName, asOf)
		{
			_detailColumnNames = detailColumnNames;
			MasterID = masterId;
		}

		/// <summary>
		/// Initializes a new instance of the EntityGetByMasterIDRequest class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type for the request.</param>
		/// <param name="detailColumnNames">The name of the detail columns for the request.</param>
		/// <param name="masterId">The ID of the master entity.</param>
		/// <param name="asOf">The effective date of the request.</param>
		/// <param name="traversal">The entity traversal describing any related entities to be included in the request.</param>
		public EntityGetByMasterIDRequest(string entityTypeName, string[] detailColumnNames, EntityID masterId, DateTime asOf, EntityTraversal traversal)
			: base(entityTypeName, asOf, traversal)
		{
			_detailColumnNames = detailColumnNames;
			MasterID = masterId;
		}

		protected override void InternalToString(StringBuilder builder)
		{
			base.InternalToString(builder);
			if (_detailColumnNames != null)
				builder.AppendFormat(", DetailColumnNames:{0}", _detailColumnNames.Combine());

			builder.AppendFormat(", MasterID:{0}", MasterID);
		}

		public override bool Equals(object obj)
		{
			var request = obj as EntityGetByMasterIDRequest;
			return (request != null)
				&& base.Equals(obj)
					&& ((_detailColumnNames == null) == (request._detailColumnNames == null))
						&& ((_detailColumnNames == null) || (_detailColumnNames.Equals(request._detailColumnNames)))
							&& (MasterID == request.MasterID);
		}

		public override int GetHashCode()
		{
			var code = base.GetHashCode();

			if (_detailColumnNames != null)
				code = _detailColumnNames.Aggregate(code, (ACurrent, AItem) => (ACurrent * 83) + AItem.GetHashCode());

			if (MasterID != null)
				code = (code * 83) + MasterID.GetHashCode();

			return code;
		}
	}
}